Skip to content

✨ feat: sign out of this device - #83

Merged
edgarmesquita merged 1 commit into
mainfrom
feat/sign-out-of-device
Sep 29, 2026
Merged

edgarmesquita merged 1 commit into
mainfrom
feat/sign-out-of-device

Conversation

@edgarmesquita

Copy link
Copy Markdown
Contributor

Edgar missed a logout. There wasn't one — and the three things that looked like one each fell short:

What it did What was wrong
Desconectar (Settings → Account) revoked the token left the vault open on screen: "stop syncing", not "leave"
Bloquear (header padlock) closed the vault kept everything, by design
Apagar dados deste dispositivo (Advanced) cleared localStorage and the stored key left the encrypted attachment cache and the invite identity on disk, and the token live in memory

clearCiphertext() and clearIdentity() already existed, with tests — nothing in the app called them.

And the old wipe's confirmation read "A cópia no Google Drive permanece intacta e pode ser baixada de novo" — to everyone, including people whose vault had never been uploaded, for whom pressing OK meant losing it forever.

What it does now

Sair deste aparelho sits beside the account at the bottom of the sidebar (inside the drawer on phones), and on the unlock screen for whoever is looking at someone else's lock screen. It removes everything of the person's from the browser — vault cache, stored key, attachment cache, invite identity, account, shared vaults, recent types — and revokes the token. Configuration (client ids) and looks (theme) stay.

It will not destroy data to do it

assessSignOut is a pure function and the heart of this:

  • nothing the cloud lacks → confirm, saying where the vault stays and how to come back;
  • edits not yet uploaded → one sync is attempted first; if it cannot land, the person is told why ("sem internet", "a conta não está conectada") before anything is deleted;
  • a vault that was never uploaded → signing out refuses. It is the only copy. The wipe in Advanced can still remove it, after saying it is gone for good.

Both doors go through signOutFlow, so the words cannot drift between them.

Knowing what is pending after a reload needed the pending flag to survive one. The sync loop kept it in memory only; it is now written synchronously beside that, through one setPending helper at all five sites.

Tests

Unit: assessSignOut and the prompt wording (8), plus wipeLocalData pinned to what it removes and what it keeps — a fork's client id and the theme must survive. Three mutations, three failures.

Smoke, eight new checks, reading the disk rather than the screen — the smoke vault was never uploaded, which makes it the case that matters most:

(antes de sair:   {"cache":true, "recentTypes":true, "attachments":1,"identity":1,"keystore":1})
PASS  sair recusa apagar um cofre que só existe aqui
PASS  e o cofre continua inteiro depois da recusa
PASS  apagar diz que um cofre sem cópia se vai para sempre
(depois de apagar:{"cache":false,"recentTypes":false,"attachments":0,"identity":0,"keystore":0})

The first check exists so the others cannot pass on a device that never had anything to delete.

My first mutation run lied to me. Deleting the two calls left their imports unused, tsc failed inside npm run build, vite build never ran, and the smoke tested the old dist/ — green. Rerun with a mutation that still compiles, and a changed bundle hash confirmed:

(depois de apagar: {"cache":false,"recentTypes":false,"attachments":1,"identity":1,"keystore":0})
FAIL  apagar leva os anexos cifrados guardados no aparelho
FAIL  apagar leva a identidade de convites deste aparelho

Two unrelated smoke failures in the final run

vence em breve traz só o cartão a renovar and badge de validade aparece na linha também no celular failed because I ran the suite at 00:03. They are a pre-existing DST bug in daysUntil: it divides milliseconds by 24 hours, and 25 October 2026 is the night Portugal turns the clock back, a 25-hour day. Between 00:00 and 01:00, any date past a DST fallback counts one day too many:

30/09 00:03  → 25/10: 26 dias  (o certo são 25)
30/09 01:30  → 25/10: 25 dias

Not part of this change. Fixing it separately.

npm test 400 green · the eight sign-out checks green.

Also fixed while here: the unlock screen's reconnect button said "Reconectar conta Google" to OneDrive users.

There was no way to leave. "Desconectar" revoked the token and left the
vault open on screen; "Bloquear" kept everything; and the wipe in
Advanced, the closest thing to a sign-out, left the encrypted
attachment cache and the invite identity on disk, with the token still
live in memory. Its confirmation also promised that "the copy in the
cloud stays intact" to people whose vault had never been uploaded.

Sair deste aparelho now sits beside the account in the sidebar, and on
the unlock screen for whoever is looking at someone else's. It removes
everything of the person's from the browser — vault cache, stored key,
attachment cache, invite identity, account, shared vaults, recent
types — and revokes the token. Configuration and looks stay.

It will not destroy data to do it. Pending edits are synced first, and
if they cannot go up the person is told why before anything is
deleted. A vault that was never uploaded is the only copy there is, so
signing out refuses outright; the wipe in Advanced can still remove
it, after saying in so many words that it is gone for good. Both doors
go through one implementation and one set of words.

Knowing what is pending after a reload needed the pending-sync flag to
survive one, so it is now written synchronously beside the in-memory
copy the sync loop already kept.
@edgarmesquita
edgarmesquita merged commit 09e1bd7 into main Sep 29, 2026
2 of 3 checks passed
@edgarmesquita
edgarmesquita deleted the feat/sign-out-of-device branch September 29, 2026 23:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant